// Package collector provides functionality for collecting metrics from libvirt
package collector

import (
	"fmt"
	"sync"
	"time"

	"gitee.com/openeuler/uos-libvirtd-exporter/slogx"
	"github.com/prometheus/client_golang/prometheus"
	"libvirt.org/go/libvirt"
)

// Collector defines the interface for collecting metrics from a domain
// Each implementation should collect a specific type of metrics
type Collector interface {
	// Describe sends the super-set of all possible descriptors of metrics
	// collected by this Collector to the provided channel
	Describe(ch chan<- *prometheus.Desc)

	// Collect retrieves metrics from the domain and sends them to the provided channel
	Collect(
		ch chan<- prometheus.Metric,
		conn *libvirt.Connect,
		domain *libvirt.Domain,
	)

	// Reset any internal state between scrapes to ensure clean state for each collection cycle
	Reset()
}

// LibvirtCollector is the main collector that orchestrates metrics collection
// from all individual collectors and manages the libvirt connection
type LibvirtCollector struct {
	uri               string             // libvirt connection URI
	conn              *libvirt.Connect   // libvirt connection handle
	mutex             sync.RWMutex       // mutex to protect connection and collectors
	collectors        []Collector        // list of individual metric collectors
	reconnectErr      chan error         // channel for reconnection errors
	exporterCollector *ExporterCollector // self-monitoring metrics collector
}

// NewLibvirtCollector creates a new LibvirtCollector and establishes connection to libvirt
func NewLibvirtCollector(uri string) (*LibvirtCollector, error) {
	slogx.Info("Establishing libvirt connection", "uri", uri)
	conn, err := libvirt.NewConnect(uri)
	if err != nil {
		slogx.Error("Failed to connect to libvirt", "uri", uri, "error", err)
		return nil, err
	}

	alive, err := conn.IsAlive()
	if err != nil || !alive {
		slogx.Error("Libvirt connection is not alive", "uri", uri)
		return nil, fmt.Errorf("connection is not alive")
	}

	slogx.Info("Libvirt connection established successfully", "uri", uri)

	collector := &LibvirtCollector{
		uri:          uri,
		conn:         conn,
		reconnectErr: make(chan error),
	}

	// Initialize individual collectors for different metric types
	slogx.Debug("Initializing metric collectors")
	collector.exporterCollector = NewExporterCollector()
	collector.collectors = append(collector.collectors, collector.exporterCollector)
	collector.collectors = append(collector.collectors, NewDomainInfoCollector())
	collector.collectors = append(collector.collectors, NewCPUCollector())
	collector.collectors = append(collector.collectors, NewMemoryCollector())
	collector.collectors = append(collector.collectors, NewDiskCollector())
	collector.collectors = append(collector.collectors, NewNetworkCollector())
	collector.collectors = append(collector.collectors, NewDeviceCollector())
	collector.collectors = append(collector.collectors, NewConnectionCollector())
	slogx.Debug("Metric collectors initialized", "count", len(collector.collectors))

	return collector, nil
}

// GetURI returns the libvirt connection URI
func (c *LibvirtCollector) GetURI() string {
	c.mutex.RLock()
	defer c.mutex.RUnlock()
	return c.uri
}

// Describe implements the prometheus.Collector interface
// It sends all descriptors of metrics that can be collected to the provided channel
func (c *LibvirtCollector) Describe(ch chan<- *prometheus.Desc) {
	for _, collector := range c.collectors {
		collector.Describe(ch)
	}
}

// Collect implements the prometheus.Collector interface
// It collects metrics from all domains and sends them to the provided channel
func (c *LibvirtCollector) Collect(ch chan<- prometheus.Metric) {
	c.mutex.Lock()
	defer c.mutex.Unlock()

	startTime := time.Now()
	slogx.Debug("Starting metrics collection")

	// Check connection health and attempt reconnection if needed
	alive, err := c.conn.IsAlive()
	if err != nil || !alive {
		slogx.Warn("Connection to libvirt lost, attempting reconnection", "uri", c.uri)
		if _, err := c.conn.Close(); err != nil {
			slogx.Error("Error closing libvirt connection", "uri", c.uri, "error", err)
		}

		conn, err := libvirt.NewConnect(c.uri)
		if err != nil {
			slogx.Error("Failed to reconnect to libvirt", "uri", c.uri, "error", err)
			return
		}
		c.conn = conn
		slogx.Info("Libvirt connection restored successfully", "uri", c.uri)
	}

	// Log connection health check result
	slogx.Debug("Libvirt connection health check completed",
		"alive", alive,
		"uri", c.uri)

	// Get all domains (both active and inactive)
	domains, err := c.conn.ListAllDomains(
		libvirt.CONNECT_LIST_DOMAINS_ACTIVE | libvirt.CONNECT_LIST_DOMAINS_INACTIVE,
	)
	if err != nil {
		slogx.Error("Failed to list domains", "error", err)
		return
	}
	defer func() {
		for _, domain := range domains {
			if err := domain.Free(); err != nil {
				slogx.Info("Error freeing domain: %v", err)
			}
		}
	}()

	slogx.Debug("Domain discovery completed", "count", len(domains))

	// Count active vs inactive domains
	activeCount := 0
	for _, domain := range domains {
		active, _ := domain.IsActive()
		if active {
			activeCount++
		}
	}
	slogx.Info("Domain inventory",
		"total_domains", len(domains),
		"active_domains", activeCount,
		"inactive_domains", len(domains)-activeCount)

	// Reset all collectors to prepare for a new scrape cycle
	slogx.Debug("Resetting collectors for new scrape cycle")
	for _, collector := range c.collectors {
		collector.Reset()
	}

	// Collect domain metrics from each domain
	for i, domain := range domains {
		domainName, _ := domain.GetName()
		domainID, _ := domain.GetID()
		active, _ := domain.IsActive()
		slogx.Debug("Processing domain metrics",
			"index", i,
			"name", domainName,
			"id", domainID,
			"active", active)

		// Use individual collectors to gather different types of metrics
		for _, collector := range c.collectors {
			slogx.Debug("Executing collector",
				"domain", domainName,
				"collector_type", fmt.Sprintf("%T", collector))
			collector.Collect(ch, c.conn, &domain)
		}
	}

	// Update exporter metrics with the number of domains found
	if c.exporterCollector != nil {
		c.exporterCollector.SetDomainsFound(len(domains))
	}

	elapsed := time.Since(startTime)
	slogx.Info("Metrics collection cycle completed",
		"domains_processed", len(domains),
		"active_domains", activeCount,
		"inactive_domains", len(domains)-activeCount,
		"collectors_executed", len(c.collectors),
		"duration_ms", elapsed.Milliseconds(),
		"collection_rate", fmt.Sprintf("%.2f domains/sec", float64(len(domains))/elapsed.Seconds()))
}

// Close closes the libvirt connection
func (c *LibvirtCollector) Close() {
	if c.conn != nil {
		slogx.Info("Closing libvirt connection")
		if _, err := c.conn.Close(); err != nil {
			slogx.Error("Error closing libvirt connection", "uri", c.uri, "error", err)
		}
		slogx.Info("Libvirt connection closed successfully")
	} else {
		slogx.Debug("No libvirt connection to close")
	}
}

// GetDomainCounts returns the total number of domains and the number of active domains
func (c *LibvirtCollector) GetDomainCounts() (total int, active int, err error) {
	c.mutex.Lock()
	defer c.mutex.Unlock()

	// Check connection health and attempt reconnection if needed
	alive, err := c.conn.IsAlive()
	if err != nil || !alive {
		slogx.Warn("Connection to libvirt lost when getting domain counts", "uri", c.uri)
		if _, err := c.conn.Close(); err != nil {
			slogx.Error("Error closing libvirt connection", "uri", c.uri, "error", err)
		}

		conn, err := libvirt.NewConnect(c.uri)
		if err != nil {
			slogx.Error("Failed to reconnect to libvirt when getting domain counts", "uri", c.uri, "error", err)
			return 0, 0, err
		}
		c.conn = conn
		slogx.Info("Libvirt connection restored successfully when getting domain counts", "uri", c.uri)
	}

	// Get all domains (both active and inactive)
	domains, err := c.conn.ListAllDomains(
		libvirt.CONNECT_LIST_DOMAINS_ACTIVE | libvirt.CONNECT_LIST_DOMAINS_INACTIVE,
	)
	if err != nil {
		slogx.Error("Failed to list domains when getting domain counts", "error", err)
		return 0, 0, err
	}
	defer func() {
		for _, domain := range domains {
			if err := domain.Free(); err != nil {
				slogx.Info("Error freeing domain: %v", err)
			}
		}
	}()

	total = len(domains)
	active = 0
	for _, domain := range domains {
		isActive, _ := domain.IsActive()
		if isActive {
			active++
		}
	}

	return total, active, nil
}
